﻿// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. 
// See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading.Actors;
using System.Diagnostics.Contracts;

namespace System.Threading.Actors.Helpers
{
    /// <summary>
    /// Static methods for checking if a key is already in use and if it is a collection type
    /// </summary>
    /// <remarks>
    /// object with name $TYPE$keyname has name of type.
    /// Actor methods should check if key already exists, and is of the expected type
    /// before doing operations
    /// </remarks>
    public static class ActorKeyType
    {
        private const string keyTypePrefix = "$TYPE$";

        /// <summary>
        /// Get type name for key
        /// </summary>
        /// <param name="state"></param>
        /// <param name="keyName"></param>
        /// <returns>key not found: null, un-typed object: empty string, typed object: type</returns>
        public static string GetKeyType(IActorState state, string keyName)
        {
            Contract.Requires(state != null);

            string keyTypeName = keyTypePrefix + keyName;
            object tempObject;

            string keyType = null;
            if (state.TryGet(keyTypeName, out tempObject))
            {
                keyType = (string)tempObject;
            }
            else if (state.TryGet(keyName, out tempObject))
            {
                keyType = String.Empty;
            }
            return keyType;
        }

        // Dynamic equivalent of above
        public static string GetKeyType(dynamic state, string keyName)
        {
            string keyTypeName = keyTypePrefix + keyName;

            string keyType = null;
            if (state.Contains(keyTypeName)) keyType = state[keyTypeName];
            else if (state.Contains(keyName)) keyType = String.Empty;
            return keyType;
        }

        /// <summary>
        /// Set the type name for key
        /// </summary>
        /// <param name="state"></param>
        /// <param name="keyName"></param>
        /// <param name="keyType"></param>
        public static void SetKeyType(IActorState state, string keyName, string keyType)
        {
            Contract.Requires(state != null);

            string keyTypeName = keyTypePrefix + keyName;
            state.Set(keyTypeName, keyType);
        }

        // Dynamic equivalent of above
        public static void SetKeyType(dynamic state, string keyName, string keyType)
        {
            string keyTypeName = keyTypePrefix + keyName;
            state[keyTypeName] = keyType;
        }

        /// <summary>
        /// Remove the type name for key
        /// </summary>
        /// <param name="state"></param>
        /// <param name="keyName"></param>
        public static void RemoveKeyType(IActorState state, string keyName)
        {
            Contract.Requires(state != null);

            string keyTypeName = keyTypePrefix + keyName;
            state.Remove(keyTypeName);
        }

        // Dynamic equivalent of above
        public static void RemoveKeyType(dynamic state, string keyName)
        {
            string keyTypeName = keyTypePrefix + keyName;
            state.Remove(keyTypeName);
        }

        /// <summary>
        /// validate that key is of the expected type
        /// </summary>
        /// <param name="state"></param>
        /// <param name="keyName"></param>
        /// <param name="keyType"></param>
        /// <param name="newOK">if false, then key must exist</param>
        /// <returns>true if key exists, false if not</returns>
        public static bool ValidateKeyType(IActorState state, string keyName, string keyType, bool newOK)
        {
            Contract.Requires(state != null);

            string keyTypeExist = GetKeyType(state, keyName);
            if (keyTypeExist == null)
            {
                if (!newOK)
                {
                    throw new InvalidOperationException(String.Format("Attempted operation on non-existant key {0}", keyName));
                }
            }
            else if (keyTypeExist != keyType)
            {
                throw new InvalidOperationException(String.Format("Attempted {0} type operation on {1} type key {2}", keyType, keyTypeExist, keyName));
            }

            return keyTypeExist != null;
        }

        // Dynamic equivalent of above
        public static bool ValidateKeyType(dynamic state, string keyName, string keyType, bool newOK)
        {
            string keyTypeExist = GetKeyType(state, keyName);
            if (keyTypeExist == null)
            {
                if (!newOK)
                {
                    throw new InvalidOperationException(String.Format("Attempted operation on non-existant key {0}", keyName));
                }
            }
            else if (keyTypeExist != keyType)
            {
                throw new InvalidOperationException(String.Format("Attempted {0} type operation on {1} type key {2}", keyType, keyTypeExist, keyName));
            }

            return keyTypeExist != null;
        }
    }
}
